/*
epoll的优点：
1、接口使用方便，epoll将接口分为了三个：epoll_create, epoll_wait, epoll_ctl，
不需要每次循环都设置需要关系的文件描述符，也做到了输入输出参数分离；
2、数据拷贝轻量，只在合适的时候调用EPOLL_CTL_ADD，将文件描述符拷贝到内核当中，
不像select和poll每次循环都需要进行拷贝
3、事件回调机制：使用回调函数的方式，将就绪事件添加到就绪队列中，epoll_wait直接
去访问已经就绪的时间，时间复杂度是O（1）
4、没有数目限制
*/


#pragma once

#include <iostream>
#include <memory>
#include <unistd.h>
#include <sys/epoll.h>
#include "Socket.hpp"

using namespace SocketModule;

class EpollServer
{
    static const int size = 4096;
    static const int default_fd = -1;

public:
    EpollServer(int port)
        : _listensock(std::make_unique<TcpSocket>()), _isrunning(false)
    {
        // 1.先创建套接字
        _listensock->BuildListenSocket(port);
        // 2.创建epoll模型
        _epfd = epoll_create(256);
        if (_epfd < 0)
        {
            std::cout << "epoll fail" << std::endl;
            exit(1);
        }
        std::cout << "epoll sucess" << _epfd << std::endl;

        // 3.将listensock设置到os内核当中
        struct epoll_event ev;
        ev.data.fd = _listensock->Fd(); // 要关心的文件描述符设置进data当中
        ev.events = EPOLLIN;
        int n = epoll_ctl(_epfd, EPOLL_CTL_ADD, _listensock->Fd(), &ev);
        if (n < 0)
        {
            std::cout << "EP_CTL_ADD fail" << std::endl;
            exit(1);
        }
        std::cout << "EP_CTL_Add sucess" << std::endl;
    }

    void Start()
    {
        int timeout = -1;
        _isrunning = true;
        while (_isrunning)
        {
            // 不能直接accept，应该获取事件到我们的epoll_event[]缓冲区中
            int n = epoll_wait(_epfd, _revs, size, timeout);
            switch (n)
            {
            case 0:
                std::cout << "out of time" << std::endl;
                break;
            case -1:
                std::cout << "epoll_wait fail" << std::endl;
                break;
            default:
                Dispatch(n); // n表示有几个fd就绪了
                break;
            }
        }
        _isrunning = false;
    }

    // 事件派发器
    void Dispatch(int rnum)
    {
        std::cout << "link ready" << std::endl;
        for (int i = 0; i < rnum; i++)
        {
            if (_revs[i].events & EPOLLIN)
            {
                if (_listensock->Fd() == _revs[i].data.fd)
                {
                    Accepter();
                }
                else
                {
                    Recver(_revs[i].data.fd);
                }
            }
            else if(_revs[i].events & EPOLLOUT)
            {
                //写事件就绪的处理方法
            }
        }
    }

    void Accepter()
    {
        InitAddr client;
        int sockfd = _listensock->Accept(client);
        if (sockfd > 0)
        {
            // 将文件描述符设置进os内核当中，因为fd是否就绪只有epoll知道
            std::cout << "gotton a new link : " << sockfd << std::endl;

            struct epoll_event ev;
            ev.data.fd = sockfd;
            ev.events = EPOLLIN;
            int n = epoll_ctl(_epfd, EPOLL_CTL_ADD, sockfd, &ev);
            if (n < 0)
            {
                std::cout << "new fd adding fail" << std::endl;
                exit(1);
            }
            else
            {
                std::cout << "epoll_ctl_add success" << std::endl;
            }
        }
    }

    void Recver(int sockfd)
    {
        char buffer[1024]; //这里有一个问题，buffer被多个fd共同使用，而且大小不一定够用，有bug
        int n = recv(sockfd, buffer, sizeof(buffer) - 1, 0);
        if(n < 0)
        {
            std::cout << "recv fail" << std::endl;
            exit(1);
        }
        else if(n == 0)
        {
            //先将fd从epoll_event中移除，再关闭文件描述符
            int n = epoll_ctl(_epfd, EPOLL_CTL_DEL, sockfd, nullptr);
            if(n > 0)
            {
                std::cout << "close success" << std::endl;
            }
            else
            {
                std::cout << "close fail" << std::endl;
            }
            close(sockfd);
        }
        else
        {
            std::cout << buffer << std::endl;
        }
    }

private:
    std::unique_ptr<Socket> _listensock;
    bool _isrunning = false;
    int _epfd;

    struct epoll_event _revs[size];
};